﻿using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;

namespace Fast.Extensions.DatabaseAccessor.EntityFrameworkCore
{
    /// <summary>
    /// 数据库访问选项类
    /// </summary>
    [SkipScan]
    public class DatabaseAccessorOptions
    {
        public IServiceCollection Services { get; }
        public Action<IRelationalDbContextOptionsBuilderInfrastructure> MigrationsAssemblyAction;
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="services"></param>
        /// <param name="migrationAssemblyName"></param>
        public DatabaseAccessorOptions(IServiceCollection services, string migrationAssemblyName)
        {
            Services = services;
            if (!string.IsNullOrWhiteSpace(migrationAssemblyName))
                MigrationsAssemblyAction = options => options.GetType().GetMethod("MigrationsAssembly").Invoke(options, new[] { migrationAssemblyName });
        }

        /// <summary>
        /// 添加数据库上下文
        /// </summary>
        /// <typeparam name="TContext"></typeparam>
        /// <param name="databaseProvider"></param>
        /// <returns></returns>
        public DatabaseAccessorOptions AddDbContext<TDbContext>(DatabaseProvider databaseProvider, Action<DbContextOptionsBuilder> optionsAction = null)
              where TDbContext : DbContext
        {
            return AddDbContext<TDbContext, DefaultDbContextLocator>(databaseProvider, optionsAction);
        }

        /// <summary>
        /// 添加数据库上下文
        /// </summary>
        /// <typeparam name="TDbContext"></typeparam>
        /// <typeparam name="TDbContextLocator"></typeparam>
        /// <param name="databaseProvider"></param>
        /// <returns></returns>
        public DatabaseAccessorOptions AddDbContext<TDbContext, TDbContextLocator>(DatabaseProvider databaseProvider, Action<DbContextOptionsBuilder> optionsAction = null)
            where TDbContext : DbContext
            where TDbContextLocator : class, IDbContextLocator
        {
            DatabaseHelper.AddMapingOfDbContextAndDbContextLocator<TDbContextLocator, TDbContext>();

            Services.AddDbContext<TDbContext>(options =>
            {
                ConfigureDatabase<TDbContext>(databaseProvider, options);

                optionsAction?.Invoke(options);
            });

            return this;
        }

        /// <summary>
        /// 添加上下文池
        /// </summary>
        /// <typeparam name="TDbContext"></typeparam>
        /// <param name="databaseProvider"></param>
        /// <param name="optionsAction"></param>
        /// <param name="poolSize"></param>
        /// <returns></returns>
        public DatabaseAccessorOptions AddDbContextPool<TDbContext>(DatabaseProvider databaseProvider, Action<DbContextOptionsBuilder> optionsAction = null, int poolSize = 1024) where TDbContext : DbContext
        {
            return AddDbContextPool<TDbContext, DefaultDbContextLocator>(databaseProvider, optionsAction, poolSize);
        }

        /// <summary>
        /// 添加上下文池
        /// </summary>
        /// <typeparam name="TDbContext"></typeparam>
        /// <param name="databaseProvider"></param>
        /// <param name="optionsAction"></param>
        /// <param name="poolSize"></param>
        /// <returns></returns>
        public DatabaseAccessorOptions AddDbContextPool<TDbContext, TDbContextLocator>(DatabaseProvider databaseProvider, Action<DbContextOptionsBuilder> optionsAction = null, int poolSize = 1024)
            where TDbContext : DbContext
            where TDbContextLocator : class, IDbContextLocator
        {
            DatabaseHelper.AddMapingOfDbContextAndDbContextLocator<TDbContextLocator, TDbContext>();

            Services.AddDbContextPool<TDbContext>((serviceProvider, options) =>
            {
                ConfigureDbContext(serviceProvider, options);

                ConfigureDatabase<TDbContext>(databaseProvider, options);

                optionsAction?.Invoke(options);

            }, poolSize);

            return this;
        }

        /// <summary>
        /// 配置数据库上下文
        /// </summary>
        /// <param name="serviceProvider"></param>
        /// <param name="options"></param>
        private void ConfigureDbContext(IServiceProvider serviceProvider, DbContextOptionsBuilder options)
        {
            var interceptorList = new List<IInterceptor> { new DbContextSaveChangesInterceptor() };
            options.AddInterceptors(interceptorList.ToArray());
        }

        /// <summary>
        /// 配置数据库
        /// </summary>
        /// <typeparam name="TContext"></typeparam>
        /// <param name="databaseProvider"></param>
        /// <param name="options"></param>
        /// <exception cref="ArgumentNullException"></exception>
        private void ConfigureDatabase<TContext>(DatabaseProvider databaseProvider, DbContextOptionsBuilder options)
        {
            var connectionOptions = App.GetConfig<DatabaseConnectionOptions>(nameof(DatabaseConnectionOptions).Replace(nameof(Options), ""), true);
            var connectionStrings = connectionOptions.ConnectionStrings;

            if (connectionStrings.Count == 0)
            {
                throw new ArgumentNullException(nameof(DatabaseConnectionOptions.ConnectionStrings));
            }
            var connectionName = ConnectionStrings.DefaultConnectionStringName;

            if (typeof(TContext).IsDefined(typeof(ConnectionStringAttribute), false))
            {
                var name = ConnectionStringAttribute.GetConnStringName<TContext>();
                if (!string.IsNullOrWhiteSpace(name)) connectionName = name;
            }
            var useMethod = GetDbContextUseMethod(databaseProvider);
            useMethod.Invoke(null, new object[] { options, connectionStrings[connectionName], MigrationsAssemblyAction });
        }

        /// <summary>
        /// 获取数据库上下文使用相关数据库方法
        /// </summary>
        /// <param name="databaseProvider"></param>
        /// <returns></returns>
        private MethodInfo GetDbContextUseMethod(DatabaseProvider databaseProvider)
        {
            var assemblyName = databaseProvider switch
            {
                DatabaseProvider.MySQL => DatabaseHelper.MySQL,
                DatabaseProvider.MySql => DatabaseHelper.MySql,
                DatabaseProvider.SqlServer => DatabaseHelper.SqlServer,
                DatabaseProvider.Oracle => DatabaseHelper.Oracle,
                DatabaseProvider.PostgreSql => DatabaseHelper.PostgreSql,
                DatabaseProvider.Sqlite => DatabaseHelper.Sqlite,
                DatabaseProvider.InMemory => DatabaseHelper.InMemory,
                _ => null
            };
            var databaseProviderAssembly = AssemblyLoadContext.Default.LoadFromAssemblyName(new AssemblyName(assemblyName));

            var extensionTypeNameWithDatabaseProviderAssembly = databaseProvider switch
            {
                DatabaseProvider.MySQL => "MySQLDbContextOptionsExtensions",
                DatabaseProvider.MySql => "",
                DatabaseProvider.SqlServer => "SqlServerDbContextOptionsExtensions",
                DatabaseProvider.Oracle => "",
                DatabaseProvider.PostgreSql => "",
                DatabaseProvider.Sqlite => "",
                DatabaseProvider.InMemory => "",
                _ => null
            };

            var databaseOptionstype = databaseProviderAssembly.GetType($"Microsoft.EntityFrameworkCore.{extensionTypeNameWithDatabaseProviderAssembly}");

            var useMethod = databaseOptionstype
                    .GetMethods(BindingFlags.Public | BindingFlags.Static)
                    .FirstOrDefault(u => u.Name == $"Use{databaseProvider}" && !u.IsGenericMethod && u.GetParameters().Length == 3 && u.GetParameters()[1].ParameterType == typeof(string));
            return useMethod;
        }
    }
}
